/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.jms;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import javax.jms.BytesMessage;
import javax.jms.JMSException;
import javax.jms.MessageFormatException;
/**
   This class implements the BytesMessage specified in the JMS specification.
   @author Shrideep Pallickara
   $Revision$
   $Date$
*/


public class JmsBytesMessage extends JmsMessage implements BytesMessage {

  ByteArrayInputStream byteInputStream;
  ByteArrayOutputStream byteOutputStream;

  DataInputStream dataInputStream;
  DataOutputStream dataOutputStream;
  
  boolean inputStreamsInitialized = false;
  boolean outputStreamsInitialized = false;
  byte[] messageBytes;
  
  public JmsBytesMessage(byte[] _messageBytes) {
    ByteArrayInputStream baInputStream = 
      new ByteArrayInputStream(_messageBytes);
    DataInputStream din = new DataInputStream(baInputStream);
    
    try {
      int headerAndPropertyLength = din.readInt(); 
      byte[] headerPropBytes = new byte[headerAndPropertyLength];
      din.readFully(headerPropBytes);
      super.unmarshallJmsMessage(headerPropBytes);
      
      /** Proceed to unmarshall the bytes */
      messageBytes = new byte[din.readInt()];
      din.readFully(messageBytes);
      initializeOutputStreams();
      initializeInputStreams();
      dataOutputStream.write(messageBytes);
      
    } catch (IOException ioe) {
      System.out.println("JmsBytesMessage:: Error in unmarshalling");
    }
    
    try {
      setJMSType("BytesMessage");
    } catch (JMSException jmsEx) {
      System.out.println(jmsEx);
    }
  }
  
  
  public JmsBytesMessage() {
    super();
    try {
      setJMSType("BytesMessage");
    } catch (JMSException jmsEx) {
      System.out.println(jmsEx);
    }
  }
  
  private void initializeInputStreams() {
    byteInputStream = new ByteArrayInputStream(messageBytes);
    dataInputStream = new DataInputStream(byteInputStream);
    inputStreamsInitialized = true;
  }
  
  private void initializeOutputStreams() {
    byteOutputStream = new ByteArrayOutputStream();
    dataOutputStream = new DataOutputStream(byteOutputStream);
    outputStreamsInitialized = true;
  }
  
  public void reset() throws JMSException {
    /** This method resets both the messageProperties and messageBody to
	readOnly */
    setBodyAndPropertyAsReadOnly();
    
    try {
      if (dataOutputStream != null) {
	dataOutputStream.flush();
	messageBytes = byteOutputStream.toByteArray();
	byteOutputStream = null;
	dataOutputStream.close();
	dataOutputStream = null;
	outputStreamsInitialized = false;
      } 
      
      if (dataInputStream !=null) {
	byteInputStream = null;
	dataInputStream.close();
	dataInputStream = null;
	inputStreamsInitialized = false;
      }
      
    } catch (IOException ioe) {
      throw new JMSException("JmsBytesMessage" + ioe);
    }
  }
  
  
  /////////////////////Boolean Read and Writes////////////////////////////
  public boolean readBoolean() throws JMSException {
    /** Do a check to see if we are allowed to read the contents of the
	message body. This message throws a MessageNotReadableException if
	the read is not allowed.*/
    testReadBody();
    initBeforeReads();
    /** Initialize the input streams, this would be done only once per 
	message */
    if (!inputStreamsInitialized) {
      initializeInputStreams();
    }
    boolean returnValue;
    try {
      returnValue = dataInputStream.readBoolean();
    } catch (IOException exception) {
      throw new JMSException("Problems reading the InputStreams " + 
			     exception);
    }
    return returnValue;
  }
  
  
  public void writeBoolean(boolean value) throws JMSException {
    /** Do a check to see if we are allowed to make changes to the body of 
	the message. This method throws a MessageNotWritableException if the
	write is not allowed */
    testWriteBody();
    /** Initialize streams, this would be done only once. */
    if (!outputStreamsInitialized) {
      initializeOutputStreams();
    }
    
    try {
      dataOutputStream.writeBoolean(value);
    } catch (IOException exception) {
      throw new JMSException("Problems writing to OutputStreams " + 
			     exception);
    } 
  }
  
  ///////////////////////Byte Read and Writes//////////////////////////////
  public byte readByte() throws JMSException {
    /** Do a check to see if we are allowed to read the contents of the
	message body. This message throws a MessageNotReadableException if
	the read is not allowed.*/
    testReadBody();
    initBeforeReads();
    /** Initialize the input streams, this would be done only once per 
	message */
    if (!inputStreamsInitialized) {
      initializeInputStreams();
    }
    byte returnValue;
    try {
      returnValue = dataInputStream.readByte();
    } catch (IOException exception) {
      throw new JMSException("Problems reading the InputStreams " + 
			     exception);
    }
    return returnValue;
  }
  
  
  public void writeByte(byte value) throws JMSException {
    /** Do a check to see if we are allowed to make changes to the body of 
	the message. This method throws a MessageNotWritableException if the
	write is not allowed */
    testWriteBody();
    /** Initialize streams, this would be done only once. */
    if (!outputStreamsInitialized) {
      initializeOutputStreams();
    }
    
    try {
      dataOutputStream.writeByte(value);
    } catch (IOException exception) {
      throw new JMSException("Problems writing to OutputStreams " + 
			     exception);
    } 
  }
  
  
  ///////////////////////Bytes Read and Writes//////////////////////////////
  public int readBytes(byte[] value) throws JMSException {
    return readBytes(value, value.length);
  }
  
  
  public void writeBytes(byte[] value) throws JMSException {
    /** Do a check to see if we are allowed to make changes to the body of 
	the message. This method throws a MessageNotWritableException if the
	write is not allowed */
    testWriteBody();
    /** Initialize streams, this would be done only once. */
    if (!outputStreamsInitialized) {
      initializeOutputStreams();
    }
    
    try {
      dataOutputStream.write(value);
    } catch (IOException exception) {
      throw new JMSException("Problems writing to OutputStreams " + 
			     exception);
    } 
  }
  
  
  
  ///////////////////////Bytes Read and Writes//////////////////////////////
  ////////////////// Offsets and lengths specified ////////////////////////
  public int readBytes(byte[] value, int length) throws JMSException {
    /** Do a check to see if we are allowed to read the contents of the
	message body. This message throws a MessageNotReadableException if
	the read is not allowed.*/
    testReadBody();
    initBeforeReads();
    /** Initialize the input streams, this would be done only once per 
	message */
    if (!inputStreamsInitialized) {
      initializeInputStreams();
    }
    int bytesRead=-1;
    if (length < 0 || length > value.length) {
      throw new JMSException("length < 0 or > the array size");
    }

    try {
      dataInputStream.mark(length);
      int available = dataInputStream.available();
      if (available == 0) {
	/** The end of stream was reached. We are required to return -1.*/
	bytesRead= -1;
      } else if (value.length <= available) {
	bytesRead = value.length;
	dataInputStream.readFully(value);
      } else {
	dataInputStream.readFully(value, 0, available);
	bytesRead = available;
      }
      
    } catch (IOException exception) {
      throw new JMSException("Problems reading the InputStreams " + 
			     exception);
    }
    return bytesRead;
  }
  
  
  public void writeBytes(byte[] value, int offset, int length) 
    throws JMSException {
    /** Do a check to see if we are allowed to make changes to the body of 
	the message. This method throws a MessageNotWritableException if the
	write is not allowed */
    testWriteBody();
    /** Initialize streams, this would be done only once. */
    if (!outputStreamsInitialized) {
      initializeOutputStreams();
    }
    
    try {
      dataOutputStream.write(value,offset, length);
    } catch (IOException exception) {
      throw new JMSException("Problems writing to OutputStreams " + 
			     exception);
    } 
  }
  
  ///////////////////////Char Read and Writes//////////////////////////////
  public char readChar() throws JMSException {
    /** Do a check to see if we are allowed to read the contents of the
	message body. This message throws a MessageNotReadableException if
	the read is not allowed.*/
    testReadBody();
    initBeforeReads();
    /** Initialize the input streams, this would be done only once per 
	message */
    if (!inputStreamsInitialized) {
      initializeInputStreams();
    }
    char returnValue;
    try {
      returnValue = dataInputStream.readChar();
    } catch (IOException exception) {
      throw new JMSException("Problems reading the InputStreams " + 
			     exception);
    }
    return returnValue;
  }
    
  public void writeChar(char value) throws JMSException {
    /** Do a check to see if we are allowed to make changes to the body of 
	the message. This method throws a MessageNotWritableException if the
	write is not allowed */
    testWriteBody();
    /** Initialize streams, this would be done only once. */
    if (!outputStreamsInitialized) {
      initializeOutputStreams();
    }
    
    try {
      dataOutputStream.writeChar(value);
    } catch (IOException exception) {
      throw new JMSException("Problems writing to OutputStreams " + 
			     exception);
    } 
  }
  

   ///////////////////////Double Read and Writes//////////////////////////////
  public double readDouble() throws JMSException {
    /** Do a check to see if we are allowed to read the contents of the
	message body. This message throws a MessageNotReadableException if
	the read is not allowed.*/
    testReadBody();
    initBeforeReads();
    /** Initialize the input streams, this would be done only once per 
	message */
    if (!inputStreamsInitialized) {
      initializeInputStreams();
    }
    double returnValue;
    try {
      returnValue = dataInputStream.readDouble();
    } catch (IOException exception) {
      throw new JMSException("Problems reading the InputStreams " + 
			     exception);
    }
    return returnValue;
  }
  
  
  public void writeDouble(double value) throws JMSException {
    /** Do a check to see if we are allowed to make changes to the body of 
	the message. This method throws a MessageNotWritableException if the
	write is not allowed */
    testWriteBody();
    /** Initialize streams, this would be done only once. */
    if (!outputStreamsInitialized) {
      initializeOutputStreams();
    }
    
    try {
      dataOutputStream.writeDouble(value);
    } catch (IOException exception) {
      throw new JMSException("Problems writing to OutputStreams " + 
			     exception);
    } 
  }

   ///////////////////////Float Read and Writes//////////////////////////////
  public float readFloat() throws JMSException {
    /** Do a check to see if we are allowed to read the contents of the
	message body. This message throws a MessageNotReadableException if
	the read is not allowed.*/
    testReadBody();
    initBeforeReads();
    /** Initialize the input streams, this would be done only once per 
	message */
    if (!inputStreamsInitialized) {
      initializeInputStreams();
    }
    float returnValue;
    try {
      returnValue = dataInputStream.readFloat();
    } catch (IOException exception) {
      throw new JMSException("Problems reading the InputStreams " + 
			     exception);
    }
    return returnValue;
   }
  
  
  public void writeFloat(float value) throws JMSException {
    /** Do a check to see if we are allowed to make changes to the body of 
	the message. This method throws a MessageNotWritableException if the
	write is not allowed */
    testWriteBody();
    /** Initialize streams, this would be done only once. */
    if (!outputStreamsInitialized) {
      initializeOutputStreams();
    }
    
    try {
      dataOutputStream.writeFloat(value);
    } catch (IOException exception) {
      throw new JMSException("Problems writing to OutputStreams " + 
			     exception);
    } 
  }


   ///////////////////////Int Read and Writes//////////////////////////////
  public int readInt() throws JMSException {
    /** Do a check to see if we are allowed to read the contents of the
	message body. This message throws a MessageNotReadableException if
	the read is not allowed.*/
    testReadBody();
    initBeforeReads();
    /** Initialize the input streams, this would be done only once per 
	message */
    if (!inputStreamsInitialized) {
      initializeInputStreams();
    }
    int returnValue;
    try {
      returnValue = dataInputStream.readInt();
    } catch (IOException exception) {
      throw new JMSException("Problems reading the InputStreams " + 
			     exception);
    }
    return returnValue;
   }
  
  
  public void writeInt(int value) throws JMSException {
    /** Do a check to see if we are allowed to make changes to the body of 
	the message. This method throws a MessageNotWritableException if the
	write is not allowed */
    testWriteBody();
    /** Initialize streams, this would be done only once. */
    if (!outputStreamsInitialized) {
      initializeOutputStreams();
    }
    
    try {
      dataOutputStream.writeInt(value);
    } catch (IOException exception) {
      throw new JMSException("Problems writing to OutputStreams " + 
			     exception);
    } 
  }

   ///////////////////////Long Read and Writes//////////////////////////////
  public long readLong() throws JMSException {
    /** Do a check to see if we are allowed to read the contents of the
	message body. This message throws a MessageNotReadableException if
	the read is not allowed.*/
    testReadBody();
    initBeforeReads();
    /** Initialize the input streams, this would be done only once per 
	message */
    if (!inputStreamsInitialized) {
      initializeInputStreams();
    }
    long returnValue;
    try {
      returnValue = dataInputStream.readLong();
    } catch (IOException exception) {
      throw new JMSException("Problems reading the InputStreams " + 
			     exception);
    }
    return returnValue;
   }
  
  
  public void writeLong(long value) throws JMSException {
    /** Do a check to see if we are allowed to make changes to the body of 
	the message. This method throws a MessageNotWritableException if the
	write is not allowed */
    testWriteBody();
    /** Initialize streams, this would be done only once. */
    if (!outputStreamsInitialized) {
      initializeOutputStreams();
    }
    
    try {
      dataOutputStream.writeLong(value);
    } catch (IOException exception) {
      throw new JMSException("Problems writing to OutputStreams " + 
			     exception);
    } 
  }

   ///////////////////////Short Read and Writes//////////////////////////////
  public short readShort() throws JMSException {
    /** Do a check to see if we are allowed to read the contents of the
	message body. This message throws a MessageNotReadableException if
	the read is not allowed.*/
    testReadBody();
    initBeforeReads();
    /** Initialize the input streams, this would be done only once per 
	message */
    if (!inputStreamsInitialized) {
      initializeInputStreams();
    }
    short returnValue;
    try {
      returnValue = dataInputStream.readShort();
    } catch (IOException exception) {
      throw new JMSException("Problems reading the InputStreams " + 
			     exception);
    }
    return returnValue;
   }
  
  
  public void writeShort(short value) throws JMSException {
    /** Do a check to see if we are allowed to make changes to the body of 
	the message. This method throws a MessageNotWritableException if the
	write is not allowed */
    testWriteBody();
    /** Initialize streams, this would be done only once. */
    if (!outputStreamsInitialized) {
      initializeOutputStreams();
    }
    
    try {
      dataOutputStream.writeShort(value);
    } catch (IOException exception) {
      throw new JMSException("Problems writing to OutputStreams " + 
			     exception);
    } 
  }

  ///////////////////////UnsignedByte Read ///////////////////////////////
  public int readUnsignedByte() throws JMSException {
    /** Do a check to see if we are allowed to read the contents of the
	message body. This message throws a MessageNotReadableException if
	the read is not allowed.*/
    testReadBody();
    initBeforeReads();
    /** Initialize the input streams, this would be done only once per 
	message */
    if (!inputStreamsInitialized) {
      initializeInputStreams();
    }
    int returnValue;
    try {
      returnValue = dataInputStream.readUnsignedByte();
    } catch (IOException exception) {
      throw new JMSException("Problems reading the InputStreams " + 
			     exception);
    }
    return returnValue;
  }
  
  ///////////////////////UnsignedShort Read ///////////////////////////////
  public int readUnsignedShort() throws JMSException {
    /** Do a check to see if we are allowed to read the contents of the
	message body. This message throws a MessageNotReadableException if
	the read is not allowed.*/
    testReadBody();
    /** Initialize the input streams, this would be done only once per 
	message */
    if (!inputStreamsInitialized) {
      initializeInputStreams();
    }
    int returnValue;
    try {
      returnValue = dataInputStream.readUnsignedShort();
    } catch (IOException exception) {
      throw new JMSException("Problems reading the InputStreams " + 
			     exception);
    }
    return returnValue;
  }
  



  /////////////////////////UTF Read and Writes/////////////////////////////
  public String readUTF() throws JMSException {
    /** Do a check to see if we are allowed to read the contents of the
	message body. This message throws a MessageNotReadableException if
	the read is not allowed.*/
    testReadBody();
    initBeforeReads();
    /** Initialize the input streams, this would be done only once per 
	message */
    if (!inputStreamsInitialized) {
      initializeInputStreams();
    }
    String returnValue;
    try {
      returnValue = dataInputStream.readUTF();
    } catch (IOException exception) {
      throw new JMSException("Problems reading the InputStreams " + 
			     exception);
    }
    return returnValue;
  }
  
  public void writeUTF(String value) throws JMSException {
    /** Do a check to see if we are allowed to make changes to the body of 
	the message. This method throws a MessageNotWritableException if the
	write is not allowed */
    testWriteBody();
    /** Initialize streams, this would be done only once. */
    if (!outputStreamsInitialized) {
      initializeOutputStreams();
    }
    
    try {
      dataOutputStream.writeUTF(value);
    } catch (IOException exception) {
      throw new JMSException("Problems writing to OutputStreams " + 
			     exception);
    } 
  }

  public void writeObject(Object value) throws JMSException {
    if (value == null)
      throw new JMSException("The object being written is null");
    if (value instanceof Boolean) {
      writeBoolean( ((Boolean)value).booleanValue());
      return;
    }

    if (value instanceof Byte) {
      writeByte( ((Byte)value).byteValue());
      return;
    }
    if (value instanceof Double) {
      writeDouble( ((Double)value).doubleValue());
      return;
    }
    if (value instanceof Float) {
      writeFloat( ((Float)value).floatValue());
      return;
    }
    if (value instanceof Integer){
      writeInt( ((Integer)value).intValue());
      return;
    }
    if (value instanceof Long) {
      writeLong( ((Long)value).longValue());
      return;
    }
    if (value instanceof Short) {
      writeShort( ((Short)value).shortValue());
      return;
    }
    if (value instanceof byte[]) {
      writeBytes((byte[])value);
      return;
    }
    throw new MessageFormatException("The format " + value.getClass() + 
				     "is not supported");
  }

  public byte[] getBytes() {
    ByteArrayOutputStream baOutputStream = new ByteArrayOutputStream();
    DataOutputStream dout = new DataOutputStream(baOutputStream);
    byte[] marshalledBytes = null;
    try {
      byte[] headerPropBytes = super.getBytes();
      /* Write the length of the byte Stream comprising headers and properties
	 first.*/
      dout.writeInt(headerPropBytes.length);
      dout.write(headerPropBytes);

      /** Proceed to marshall the byte array */
      dataOutputStream.flush();
      messageBytes = byteOutputStream.toByteArray();

      dout.writeInt(messageBytes.length);
      dout.write(messageBytes);
      dout.flush();

      marshalledBytes = baOutputStream.toByteArray();
    } catch (IOException ioe) {
      System.out.println("JmsBytesMessage: Error marshalling BytesMessage");
    }/* end try-catch block */
    return marshalledBytes;
  }

  private void initBeforeReads() throws JMSException{
    if (dataInputStream == null) {
      initializeInputStreams();
    }
    try {
      dataInputStream.mark(messageBytes.length - dataInputStream.available());
    } catch(IOException ioe) {
      throw new JMSException("JmsBytesMessage" + ioe);
    }
  }

  public void clearBody() throws JMSException {
    initializeOutputStreams();
  }


  public static void main(String[] args) {
    try {
      JmsBytesMessage bMessage = new JmsBytesMessage();
      bMessage.writeInt(20);
      bMessage.writeLong(System.currentTimeMillis());
      
      JmsBytesMessage bMessageTwo = new JmsBytesMessage(bMessage.getBytes());
      System.out.println("Int Value = " +bMessage.readInt());
      System.out.println("Long Value = " + bMessage.readLong());
      
    } catch(JMSException jmsEx) {
      System.out.println("JmsBytesMessage:main() " + jmsEx);
    } /*end try-catch */
  }

}




